/**
  ******************************************************************************
  * @文件     oled.c
  * @作者     hero_cao
  * @版本     V1.0.0
  * @日期     2025/07/08
  * @概要     oled驱动
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "at32f403a_407_gpio.h"
#include "./oled/oled.h"
#include "./spi/spi.h"
#include "./oled/oledfont.h"

/* Private types ----------------------------------------------------------- */
static uint8_t g_oled_gram[OLED_WITH][8];


/**
  * @brief  写寄存器函数
  * @param  regval-寄存器值
  * @retval 无
  */
static void oled_wr_reg(uint8_t regval)
{   
	gpio_bits_reset(GPIOA, GPIO_PINS_6);	//OLED片DC拉低发送命令
	spi1_write_byte(regval);
}

/**
  * @brief  写数据函数
  * @param  data-要写入的值
  * @retval 无
  */
static void oled_wr_data(uint8_t data)
{
	gpio_bits_set(GPIOA, GPIO_PINS_6);		//OLED片DC拉高发送数据
	spi1_write_byte(data);
}

/**
  * @brief  ssd1306初始化指令
  * @param  无
  * @retval 无
  */
static void ssd1306_init(void)
{	  
	oled_wr_reg(0xAE);   /* 关闭显示 */
    oled_wr_reg(0xD5);   /* 设置时钟分频因子,震荡频率 */
    oled_wr_reg(0X50);   /* [3:0],分频因子;[7:4],震荡频率 */
    oled_wr_reg(0xA8);   /* 设置驱动路数 */
    oled_wr_reg(0X3F);   /* 默认0X3F(1/64) */
    oled_wr_reg(0xD3);   /* 设置显示偏移 */
    oled_wr_reg(0X00);   /* 默认为0 */

    oled_wr_reg(0x40);   /* 设置显示开始行 [5:0],行数. */

    oled_wr_reg(0x8D);   /* 电荷泵设置 */
    oled_wr_reg(0x14);   /* bit2，开启/关闭 */
    oled_wr_reg(0x20);   /* 设置内存地址模式 */
    oled_wr_reg(0x02);   /* [1:0],00，列地址模式;01，行地址模式;10,页地址模式;默认10; */
    oled_wr_reg(0xA1);   /* 段重定义设置,bit0:0,0->0;1,0->127; */
    oled_wr_reg(0xC8);   /* 设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 */
    oled_wr_reg(0xDA);   /* 设置COM硬件引脚配置 */
    oled_wr_reg(0x12);   /* [5:4]配置 */

    oled_wr_reg(0x81);   /* 对比度设置 */
    oled_wr_reg(0xEF);   /* 1~255;默认0X7F (亮度设置,越大越亮) */
    oled_wr_reg(0xD9);   /* 设置预充电周期 */
    oled_wr_reg(0xf1);   /* [3:0],PHASE 1;[7:4],PHASE 2; */
    oled_wr_reg(0xDB);   /* 设置VCOMH 电压倍率 */
    oled_wr_reg(0x30);   /* [6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; */

    oled_wr_reg(0xA4);   /* 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) */
    oled_wr_reg(0xA6);   /* 设置显示方式;bit0:1,反相显示;0,正常显示 */
    oled_wr_reg(0xAF);   /* 开启显示 */
}

/**
  * @brief  ssd1306初始化指令
  * @param  无
  * @retval 无
  */
static void test_19264_init(void)
{	  
	oled_wr_reg(0xE2);   //系统设置复位，这一句必须有
    oled_wr_reg(0x2E);   //设置电源为内部升压、电荷泵工作电流1.4mA
    oled_wr_reg(0xEB);   //设置偏压比，采用默认值，不要更改！
    oled_wr_reg(0xA1);   //设置帧数，采用默认值，0:76fps,1:95fps,2:132fps,3:168fps 
    oled_wr_reg(0x89);   //自动换行
    oled_wr_reg(0x81);   //设置对比度，该命令后必须紧跟对比度的值，取值范围0-255 
    oled_wr_reg(0x78);   //设置对比度的值为0x78

    oled_wr_reg(0xC2);   //设置行列扫描顺序，c0 默认扫描顺序，c2 翻转x，c4 翻转y，c6 翻转xy 

    oled_wr_reg(0xAF);   //开启显示
}

/**
 * @brief  更新显存到OLED
 * @param  无
 * @retval 无
 */
void oled_refresh_gram(void)
{
    uint8_t i, n;

    for (i = 0; i < 8; i++) {
        oled_wr_reg (0xb0 + i);  /* 设置页地址（0~7） */
        oled_wr_reg (0x00);      /* 设置显示位置—列低地址 */
        oled_wr_reg (0x10);      /* 设置显示位置—列高地址 */

        for (n = 0; n < OLED_WITH; n++) {
            oled_wr_data(g_oled_gram[n][i]);
        }
    }
}

/**
 * @brief  清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
 * @param  无
 * @retval 无
 */
void oled_clear(void)
{
    uint8_t i, n;

    for (i = 0; i < 8; i++) {
		for (n = 0; n < OLED_WITH; n++) {
			g_oled_gram[n][i] = 0x00;
		}
	}

    oled_refresh_gram();    /* 更新显示 */
}

/**
  * @brief  初始化oled
  * @param  无
  * @retval 无
  */
void oled_init(void)
{	
	spi1_init();
	gpio_bits_reset(GPIOA, GPIO_PINS_4);	//OLED片选拉低选中
	ssd1306_init();
	oled_clear();
} 

/**
 * @brief  OLED画点
 * @param  x  : 0~127
 * @param  y  : 0~63
 * @param  dot: 1 填充 0,清空
 * @retval 无
 */
void oled_draw_point(uint8_t x, uint8_t y, uint8_t dot)
{
    uint8_t pos, bx, temp = 0;

    if (x > 127 || y > 63) return;  /* 超出范围了. */

    pos = y / 8;        /* 计算GRAM里面的y坐标所在的字节, 每个字节可以存储8个行坐标 */

    bx = y % 8;         /* 取余数,方便计算y在对应字节里面的位置,及行(y)位置 */
    temp = 1 << bx;     /* 高位表示低行号, 得到y对应的bit位置,将该bit先置1 */

    if (dot) {                       /* 画实心点 */
    
        g_oled_gram[x][pos] |= temp;
    } else {                           /* 画空点,即不显示 */
    
        g_oled_gram[x][pos] &= ~temp;
    }
}

/**
 * @brief   OLED填充区域填充
 *   @note: 注意:需要确保: x1<=x2; y1<=y2  0<=x1<=127  0<=y1<=63
 * @param   x1,y1: 起点坐标
 * @param   x2,y2: 终点坐标
 * @param   dot: 1 填充 0,清空
 * @retval  无
 */
void oled_fill(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t dot)
{
    uint8_t x, y;

    for (x = x1; x <= x2; x++) {
        for (y = y1; y <= y2; y++) {
			oled_draw_point(x, y, dot);
		}
    }

    oled_refresh_gram();    /* 更新显示 */
}

/**
 * @brief   在指定位置显示一个字符,包括部分字符
 * @param   x   : 0~127
 * @param   y   : 0~63
 * @param   size: 选择字体 12/16/24
 * @param   mode: 0,反白显示;1,正常显示
 * @retval  无
 */
void oled_show_char(uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode)
{
    uint8_t temp, t, t1;
    uint8_t y0 = y;
    uint8_t *pfont = 0;
    uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2); /* 得到字体一个字符对应点阵集所占的字节数 */
    chr = chr - ' ';        /* 得到偏移后的值,因为字库是从空格开始存储的,第一个字符是空格 */

    if (size == 12) {        /* 调用1206字体 */
    
        pfont = (uint8_t *)oled_asc2_1206[chr];
    } else if (size == 16) {    /* 调用1608字体 */
    
        pfont = (uint8_t *)oled_asc2_1608[chr];
    } else if (size == 24) {    /* 调用2412字体 */
    
        pfont = (uint8_t *)oled_asc2_2412[chr];
    } else {                   /* 没有的字库 */
    
        return;
    }

    for (t = 0; t < csize; t++) {
        temp = pfont[t];

        for (t1 = 0; t1 < 8; t1++) {
            if (temp & 0x80)oled_draw_point(x, y, mode);
            else oled_draw_point(x, y, !mode);

            temp <<= 1;
            y++;

            if ((y - y0) == size) {
                y = y0;
                x++;
                break;
            }
        }
    }
}

/**
 * @brief   平方函数, m^n
 * @param   m: 底数
 * @param   n: 指数
 * @retval  无
 */
static uint32_t oled_pow(uint8_t m, uint8_t n)
{
    uint32_t result = 1;

    while (n--) {
        result *= m;
    }

    return result;
}

/**
 * @brief   显示len个数字
 * @param   x,y : 起始坐标
 * @param   num : 数值(0 ~ 2^32)
 * @param   len : 显示数字的位数
 * @param   size: 选择字体 12/16/24
 * @retval  无
 */
void oled_show_num(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size)
{
    uint8_t t, temp;
    uint8_t enshow = 0;

    for (t = 0; t < len; t++) {                                      /* 按总显示位数循环 */
    
        temp = (num / oled_pow(10, len - t - 1)) % 10;              /* 获取对应位的数字 */

        if (enshow == 0 && t < (len - 1)) {                          /* 没有使能显示,且还有位要显示 */
        
            if (temp == 0) {
            
                oled_show_char(x + (size / 2)*t, y, ' ', size, 1);  /* 显示空格,站位 */
                continue;                                           /* 继续下个一位 */
            } else {
                enshow = 1;                                          /* 使能显示 */
            }
        }

        oled_show_char(x + (size / 2)*t, y, temp + '0', size, 1);    /* 显示字符 */
    }
}

/**
 * @brief       显示字符串
 * @param       x,y : 起始坐标
 * @param       size: 选择字体 12/16/24
 * @param       *p  : 字符串指针,指向字符串首地址
 * @retval      无
 */
void oled_show_string(uint8_t x, uint8_t y, const char *p, uint8_t size)
{
    while ((*p <= '~') && (*p >= ' ')) {     /* 判断是不是非法字符! */
    
        if (x > (OLED_WITH - (size / 2))) {  /* 宽度越界 */
        
            x = 0;
            y += size;                      /* 换行 */
        }

        if (y > (64 - size)) {               /* 高度越界 */
        
            y = x = 0;
            oled_clear();
        }

        oled_show_char(x, y, *p, size, 1);  /* 显示一个字符 */
        x += size / 2;                      /* ASCII字符宽度为汉字宽度的一半 */
        p++;
    }
}

/* FILE END */
